package com.catl.plc.handler;

import com.catl.plc.master.AbstractPlcMaster;
import com.catl.plc.meta.RegisterTypeEnums;
import com.catl.plc.utils.CollectionUtils;
import org.apache.plc4x.java.PlcDriverManager;
import org.apache.plc4x.java.api.PlcConnection;
import org.apache.plc4x.java.api.exceptions.PlcConnectionException;
import org.apache.plc4x.java.api.messages.PlcReadRequest;
import org.apache.plc4x.java.api.messages.PlcReadResponse;
import org.apache.plc4x.java.api.messages.PlcWriteRequest;
import org.apache.plc4x.java.api.messages.PlcWriteResponse;
import org.apache.plc4x.java.api.types.PlcResponseCode;
import org.apache.plc4x.java.utils.connectionpool.PooledPlcDriverManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

public class Plc4jUtil {
    private Plc4jUtil() {
    }

    private static final Logger log = LoggerFactory.getLogger(Plc4jUtil.class);
    private static ConcurrentHashMap<String, PlcConnection> connections = new ConcurrentHashMap<>();
    private static final PlcDriverManager PLC_DRIVER_MANAGER = new PooledPlcDriverManager();
    private static final String ITEM_TYPE_START_STR = ":INT[";
    private static final String ITEM_TYPE_END_STR = "]";
    private static void getConnection(AbstractPlcMaster master, String path) throws PlcConnectionException {
        if (!connections.containsKey(path)) {
            PlcConnection plcConnection = PLC_DRIVER_MANAGER.getConnection(path);
            connections.put(path, plcConnection);
        }
    }

    public static List<Integer> read(AbstractPlcMaster master, RegisterTypeEnums type, int slaveId, int startOffset, int length) {
        long startTime = System.currentTimeMillis();
        List<Integer> result = new ArrayList<>();
        startOffset +=1;
        String startOffsetStr = startOffset+"";
        if(startOffsetStr.length() <=1){
            startOffsetStr = "0"+startOffsetStr;
        }
        try {
            String path = "modbus:tcp://" + master.getPlc4jMaster().getIp() + ":" + master.getPlc4jMaster().getPort() + "?request-timeout=" + master.getPlc4jMaster().getTimeout() + "&unit-identifier=" + slaveId;
            getConnection(master, path);
            PlcConnection plcConnection = connections.get(path);
//            PlcConnection plcConnection = PLC_DRIVER_MANAGER.getConnection(path);
//            log.info("******{}",plcConnection);
            PlcReadRequest.Builder readBuilder = plcConnection.readRequestBuilder();
            String itemName = "write";
            String item = "";
            switch (type) {
                case H00:
                    item = "0x00" + startOffsetStr + ITEM_TYPE_START_STR + length + ITEM_TYPE_END_STR;
                    break;
                case H01:
                    item = "1x00" + startOffsetStr + ITEM_TYPE_START_STR + length + ITEM_TYPE_END_STR;
                    break;
                case H03:
                    item = "3x00" + startOffsetStr + ITEM_TYPE_START_STR + length + ITEM_TYPE_END_STR;
                    break;
                case H04:
                    item = "4x00" + startOffsetStr + ITEM_TYPE_START_STR + length + ITEM_TYPE_END_STR;
                    break;
                default:
                    throw new IllegalArgumentException("无效的plc读写类型！！！");
            }
            readBuilder.addItem(itemName, item);
            PlcReadRequest readRequest = readBuilder.build();
            CompletableFuture<? extends PlcReadResponse> asyncReadResponse = readRequest.execute();
//            result = asyncReadResponse.get().getAllIntegers(itemName).stream().collect(Collectors.toList());
            result = asyncReadResponse.handleAsync((response, throwable)->{
                List<Integer> list = new ArrayList<>();
                for(String fieldName : response.getFieldNames()){
                   int numValues = response.getNumberOfValues(fieldName);
                   if(response.getResponseCode(fieldName) == PlcResponseCode.OK) {
                       if(numValues == 1) {
                           list.add(response.getInteger(fieldName));
                       } else {
                           for(int i = 0; i < numValues; i++) {
                               list.add(response.getInteger(fieldName, i));
                           }
                       }
                   }else {
                       log.info("plc4j 读取错误",response.getResponseCode(fieldName));
                       throw new IllegalArgumentException("读取错误");
                   }
               }
               return list;
            }).get(Integer.MAX_VALUE, TimeUnit.SECONDS);
            master.getOkHandler().accept(result);
        } catch (Exception e) {
            master.getErrorHandler().accept(e);
            log.error("modbus 异常:{}", e);
        }
        log.warn("[plc读取] >>> 从几号:{},起始偏移量:{},读取结果:{},耗时:{},读取数据:{},类型:{}",
                slaveId, startOffset-1, !CollectionUtils.isEmpty(result), (System.currentTimeMillis() - startTime)/1000.0,result, type.getName());
        return result;
    }

    public static boolean write(AbstractPlcMaster master, RegisterTypeEnums type, int slaveId, int startOffset, List<Integer> data) {
        long startTime = System.currentTimeMillis();
        List<Short> commands = data.stream().map(Integer::shortValue).collect(Collectors.toList());
        boolean result = false;
        startOffset +=1;
        String startOffsetStr = startOffset+"";
        if(startOffsetStr.length() <=1){
            startOffsetStr = "0"+startOffsetStr;
        }
        try {
            String path = "modbus:tcp://" + master.getPlc4jMaster().getIp() + ":" + master.getPlc4jMaster().getPort() + "?request-timeout=" + master.getPlc4jMaster().getTimeout() + "&unit-identifier=" + slaveId;
            getConnection(master, path);
            PlcConnection plcConnection = connections.get(path);
//            PlcConnection plcConnection = PLC_DRIVER_MANAGER.getConnection(path);
//            log.info("******{}",plcConnection);
            PlcWriteRequest.Builder writeRBuilder = plcConnection.writeRequestBuilder();
            String itemName = "write";
            String item = "";
            switch (type) {
                case H00:
                    item = "0x00" + startOffsetStr + ITEM_TYPE_START_STR + commands.size() + ITEM_TYPE_END_STR;
                    break;
                case H04:
                    item = "4x00" + startOffsetStr + ITEM_TYPE_START_STR + commands.size() + ITEM_TYPE_END_STR;
                    break;
                case H05:
                    item = "0x00" + startOffsetStr + ITEM_TYPE_START_STR + 1 + ITEM_TYPE_END_STR;
                    break;
                case H06:
                    item = "4x00" + startOffsetStr + ITEM_TYPE_START_STR + 1 + ITEM_TYPE_END_STR;
                    break;
                default:
                    throw new IllegalArgumentException("无效的plc读写类型！！！");
            }
            writeRBuilder.addItem(itemName, item,
                    (type.equals(RegisterTypeEnums.H05) || type.equals(RegisterTypeEnums.H06)) ? commands.subList(0, 1).toArray() : commands.toArray());
            PlcWriteRequest writeRequest = writeRBuilder.build();
            CompletableFuture<? extends PlcWriteResponse> asyncWriteResponse = writeRequest.execute();
//            result = asyncWriteResponse.get().getResponseCode(itemName) == PlcResponseCode.OK;
            result = asyncWriteResponse.handleAsync((response,throwable)->{
                for (String fieldName : response.getFieldNames()) {
                    if(response.getResponseCode(fieldName) != PlcResponseCode.OK){
                        return false;
                    }
                }
                return true;
            }).get(Integer.MAX_VALUE, TimeUnit.SECONDS);
            master.getOkHandler().accept(result);
        } catch (Exception e) {
            master.getErrorHandler().accept(e);
            log.error("modbus 异常:{}", e);
        }
        log.warn("[plc写入] >>> 从几号:{},起始偏移量:{},写入结果:{},耗时:{},写入数据:{},类型:{}",
                slaveId, startOffset-1, (System.currentTimeMillis() - startTime)/1000.0, result, commands, type.getName());
        return result;
    }
}
